Skip to content

fix(mix): accept shadow token .mix() on raw-list styler methods (sentinel-style, non-breaking)#941

Open
tilucasoli wants to merge 8 commits into
mainfrom
fix/shadow-token-mix-sentinel
Open

fix(mix): accept shadow token .mix() on raw-list styler methods (sentinel-style, non-breaking)#941
tilucasoli wants to merge 8 commits into
mainfrom
fix/shadow-token-mix-sentinel

Conversation

@tilucasoli

Copy link
Copy Markdown
Collaborator

Summary

Same goal as #932 — let BoxShadowToken('x').mix() / ShadowToken('x').mix() be passed directly to the shadow styler methods (BoxStyler.boxShadows, BoxStyler.shadows, FlexBoxStyler.shadows, StackBoxStyler.shadows, TextStyler.shadows) — but solved without changing any method signatures, so it is not a breaking change.

This is the sentinel-value approach, modeled on how DoubleRef lets a MixToken<double> flow through plain double APIs: the public type stays natural, the token ref is-a that type, and it's detected and routed at the call site.

Fixes #925.

The problem (recap of #925)

final token = BoxShadowToken('effect');
BoxStyler().boxShadows(token.mix());
// The argument type 'BoxShadowListMixRef' can't be assigned to
// the parameter type 'List<BoxShadowMix>'.

token.mix() returns a BoxShadowListMixRef (implements BoxShadowListMix), but the styler method takes a raw List<BoxShadowMix>.

Approach: sentinel-style refs (like DoubleRef)

DoubleRef is a sentinel-backed shim that implements double, so a MixToken<double> flows through any double parameter and is detected during prop construction. The signature never changes from double.

This PR applies the same idea to shadow lists:

  1. BoxShadowListMixRef now also implements List<BoxShadowMix> (and ShadowListMixRef implements List<ShadowMix>). The ref already extends Prop<List<BoxShadow>> and mixes in ValueRef (whose noSuchMethod guards the list members, which are never actually invoked). So token.mix() now satisfies the existing List<BoxShadowMix> parameter type.
  2. The styler methods keep their List<BoxShadowMix> / List<ShadowMix> signatures. They detect a token ref (it's also a BoxShadowListMix / ShadowListMix) and route it through Prop.mix(...) so the TokenSource is preserved; literal lists take the original path unchanged.

What changed

File Change
packages/mix/lib/src/theme/tokens/token_refs.dart BoxShadowListMixRef / ShadowListMixRef now also implements List<BoxShadowMix> / List<ShadowMix>.
packages/mix/lib/src/style/mixins/shadow_style_mixin.dart boxShadows detects a BoxShadowListMix token ref and routes it as a Mix; literal path unchanged. Signature unchanged.
packages/mix/lib/src/style/mixins/decoration_style_mixin.dart Same for shadows. Signature unchanged.
packages/mix/lib/src/style/mixins/text_style_mixin.dart Same for text shadows. Signature unchanged.
packages/mix/test/.../shadow_list_token_integration_test.dart New tests for .mix() through each styler, literal-list still works, and MixScope end-to-end resolution.
packages/mix/CHANGELOG.md Fix entry under the existing token-reference "Unreleased" section.

Difference from #932

#932 (wrapper type) This PR (sentinel-style)
Signature boxShadows(BoxShadowListMix) boxShadows(List<BoxShadowMix>) — unchanged
Breaking? Yes — literal callers must wrap with BoxShadowListMix([...]) No
Generated .g.dart factories regenerated untouched
mix_tailwinds parser updated to wrap untouched
Existing styler tests updated to wrap literals unchanged
// Works (token — the case from #925)
const cardShadow = BoxShadowToken('shadows.card');
BoxStyler().boxShadows(cardShadow.mix());

// Still works exactly as before (no migration)
BoxStyler().boxShadows([
  BoxShadowMix(color: Colors.black, blurRadius: 8),
]);

Test plan

  • dart analyze lib clean (mix).
  • dart analyze lib clean (mix_tailwinds) — confirms nothing downstream needed changing.
  • flutter test mix specs/style/theme suites: 545/545 passing.
  • flutter test mix_tailwinds: 322/322 passing.
  • New integration tests (17 in the shadow token file) cover: .mix() implements the raw list types, each styler accepts .mix(), literal lists still compile/work, and MixScope resolves boxShadowToken.mix() / shadowToken.mix() to the configured values at render time.

DCM was skipped locally (not installed in this environment), same as #932. Should be re-checked by CI.

🤖 Generated with Claude Code

…entinel-style refs

Make BoxShadowListMixRef / ShadowListMixRef also implement
List<BoxShadowMix> / List<ShadowMix> so boxShadowToken.mix() and
shadowToken.mix() can be passed directly to BoxStyler.boxShadows,
BoxStyler.shadows, FlexBoxStyler.shadows, StackBoxStyler.shadows, and
TextStyler.shadows. This mirrors the DoubleRef sentinel shim: the public
signatures stay as raw lists, the token ref flows through as that type,
and the styler detects it and routes it as a Mix so the TokenSource
survives resolution.

Unlike the *ListMix-wrapper approach, this keeps the styler signatures
unchanged, so existing literal-list call sites, generated factories, and
mix_tailwinds need no changes.

Fixes #925.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tilucasoli and others added 7 commits June 22, 2026 11:23
… and token .mix()

The example app under packages/mix/example shows applying box and text
shadow lists two ways: as literal List<BoxShadowMix>/List<ShadowMix>, and
via BoxShadowToken/ShadowToken `.mix()` references (BoxShadowListMix /
ShadowListMix) resolved through MixScope — the flow enabled by this branch.

Wires the in-repo mix / mix_annotations packages via dependency_overrides so
the example resolves standalone, without requiring melos bootstrap. Includes
a widget smoke test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wListMix and token .mix()"

This reverts commit bcd6f01.
Remove the deprecated .mix() helpers for ShadowToken and BoxShadowToken and related routing logic. Deleted Prop imports and the special-case branches in DecorationStyleMixin, ShadowStyleMixin, and TextStyleMixin that handled BoxShadowListMix/ShadowListMix token refs, simplifying shadow handling to use the standard call() refs. Update tests to remove assertions and integration tests that relied on the removed .mix() behavior. This cleans up token API surface and test coverage to match the simplified token reference model.
Introduce mix() helpers for ShadowToken and BoxShadowToken that return Mix-compatible refs and preserve Prop.token provenance. Update BoxDecorationMix and TextStyleMix to pass BoxShadowListMix/ShadowListMix through to Prop.mix when already a Mix so token sources aren't wrapped away. Clarify behavior in related style mixin comments. Add/extend tests to cover .mix() return types, styler acceptance, MixScope resolution, and token registry integration.
Import '../../core/prop.dart' into decoration, shadow, and text style mixins and shorten the doc comments for shadows/boxShadows methods by removing the verbose token-unwrapping explanation. This keeps the documentation concise and ensures the Prop symbol is available for these mixin files.
Remove the unused import '../../core/prop.dart' from decoration_style_mixin.dart, shadow_style_mixin.dart, and text_style_mixin.dart to clean up imports and eliminate unused-import warnings. No functional changes.

@leoafarias leoafarias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review pass focused on the shadow list token behavior.

boxShadow: boxShadow != null
? Prop.mix(BoxShadowListMix(boxShadow))
? Prop.mix(
boxShadow is BoxShadowListMix

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch currently blocks CI: with dcm analyze --fatal-warnings, boxShadow is statically List<BoxShadowMix>?, so DCM reports boxShadow is BoxShadowListMix and the following cast as unrelated. The same pattern in TextStyleMix fails too. Please route this through a DCM-safe helper or another preservation path so the token case survives without tripping analysis.

final class ShadowListMixRef extends Prop<List<Shadow>>
with ValueRef<List<Shadow>>
implements ShadowListMix {
implements ShadowListMix, List<ShadowMix> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this ref now implements the raw List<ShadowMix> type, it can be passed to every existing List<ShadowMix> constructor, not just TextStyler.shadows. Generated IconStyler(shadows:) and IconThemeModifierMix(shadows:) still do Prop.mix(ShadowListMix(shadows)), which wraps this token ref as a list item and then tries to iterate it during resolve. Please apply the same token-preserving path to those raw-list shadow constructors, including the generated source for IconStyler.

// token-carrying [Prop]); pass it straight to [Prop.mix] so its token
// source is preserved instead of being wrapped into a fresh list.
shadows: shadows != null
? Prop.mix(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This preserves pure token .mix() values, but merge resolution still loses token fields when a token list is chained with a literal list. In Prop.resolveProp, once sources include both TokenSource and MixSource, the resolved List<Shadow> must be converted back to Mix<List<Shadow>>; only individual Shadow/BoxShadow converters are registered today, so the token list is skipped. Please add list converters and tests for literal-to-token and token-to-literal merge order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

'BoxShadowListMixRef' can't be assigned to the parameter type 'List<BoxShadowMix>'

3 participants